/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.hutech09.jsale.bsn.common;

import com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.ejb.Stateless;
import javax.persistence.EntityExistsException;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.hutech09.jsale.entity.ICommonEntity;
import org.hutech09.jsale.entity.Price_;

/**
 *
 * @author Nam
 */
@Stateless
public class GenericDao implements IGenericDao {

    @PersistenceContext
    private EntityManager em;

    @Override
    public <T extends ICommonEntity> T create(T entity) throws DataExistsException, DataConstraintException, DataException {
        if (entity == null) {
            throw new IllegalArgumentException("entity can't be null.");
        }
        try {
            em.persist(entity);
            em.flush();     // needed for database update to check constraint violation
            em.refresh(entity);
            return entity;
        } catch (EntityExistsException e) {
            throw new DataExistsException(entity.toString());
        } catch (Exception e) {
            Throwable nestedCause = e;
            do {
                nestedCause = nestedCause.getCause();
                if (nestedCause instanceof MySQLIntegrityConstraintViolationException) {
                    if (nestedCause.getMessage().contains("Duplicate entry")) {
                        throw new DataExistsException(nestedCause.getMessage() + entity.toString(), e);
                    }
                }
            } while (nestedCause != null);
            throw new DataException(e);
        }
    }

    @Override
    public <T extends ICommonEntity> T update(T entity) throws DataNotExistedException, DataConstraintException, DataException {
        Object id = entity.getId();
        if (id == null) {
            throw new IllegalArgumentException("entity.id can't be null! " + entity.toString());
        }
        try {
            T retEntity = em.merge(entity);
            em.flush();
            return retEntity;
        } catch (IllegalArgumentException iae) {
            Object ent = em.find(entity.getClass(), id);
            if (ent == null) {
                throw new DataNotExistedException(entity, iae);
            } else {
                throw new DataConstraintException(iae);
            }
        } catch (Exception e) {
            throw new DataException(e);
        }
    }

    @Override
    public <T extends ICommonEntity> T delete(T entity) throws DataNotExistedException {
        if (entity == null || entity.getId() == null) {
            throw new IllegalArgumentException("entity and entity.id can't be null. " + entity.toString());
        }
        T obj = (T) em.find(entity.getClass(), entity.getId());
        if (obj == null) {
            throw new DataNotExistedException(entity);
        }
        em.remove(obj);
        return obj;
    }

    public <T extends ICommonEntity> T delete(Class<T> baseClass, Object id) throws DataNotExistedException {
        if (baseClass == null) {
            throw new IllegalArgumentException("baseClass can't be null");
        }
        if (id == null) {
            throw new IllegalArgumentException("id can't be null");
        }
        T ent = em.find(baseClass, id);
        if (ent == null) {
            throw new DataNotExistedException(baseClass.getName() + ".id = " + id);
        }
        em.remove(ent);
        return ent;
    }

    @Override
    public <T extends ICommonEntity> T find(Class<T> baseClass, Object id) throws DataException {
        if (baseClass == null) {
            throw new IllegalArgumentException("baseClass can't be null");
        }
        if (id == null) {
            throw new IllegalArgumentException("id can't be null");
        }
        try {
            T entity = em.find(baseClass, id);
            return entity;
        } catch (Exception e) {
            throw new DataException(e);
        }
    }

    @Override
    public boolean isExisted(Class<? extends ICommonEntity> baseClass, Object id) throws DataException {
        if (baseClass == null) {
            throw new IllegalArgumentException("baseClass can't be null");
        }
        if (id == null) {
            throw new IllegalArgumentException("id can't be null");
        }
        try {
            Object obj = em.find(baseClass, id);
            if (obj == null) {
                return false;
            }
            return true;
        } catch (Exception e) {
            throw new DataException(e);
        }
    }

    @Override
    public <T extends ICommonEntity> List<T> findByNamedQuery(String queryName, Map parameters) throws DataException {
        if (queryName == null) {
            throw new IllegalArgumentException("queryName can't be null");
        }
        try {
            Query query = em.createNamedQuery(queryName);
            if (parameters != null && !parameters.isEmpty()) {
                String conditionKey;
                Object conditionValue;
                Iterator iterator = parameters.keySet().iterator();
                while (iterator.hasNext()) {
                    conditionKey = (String) iterator.next();
                    conditionValue = parameters.get(conditionKey);
                    query.setParameter(conditionKey, conditionValue);
                }
            }
            return query.getResultList();
        } catch (Exception e) {
            throw new DataException(e);
        }
    }

    @Override
    public <T extends ICommonEntity> T getOneByNamedQuery(String queryName, Map parameters) throws DataException {
        List<T> list = findByNamedQuery(queryName, parameters);
        if (list == null || list.isEmpty()) {
            return null;
        }
        return list.get(0);
    }

    @Override
    public <T extends ICommonEntity> List<T> getAll(Class<T> clazz) {
        if (clazz == null) {
            throw new IllegalArgumentException("clazz can't be null.");
        }
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<T> query = builder.createQuery(clazz);
        Root<T> entity = query.from(clazz);
        query.select(entity);
        TypedQuery<T> q = em.createQuery(query);
        return q.getResultList();
    }

    @Override
    public <T extends ICommonEntity> List<T> findPaging(Class<T> clazz, int firstIndex, int pageSize, String orderField, boolean desc) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<T> query = builder.createQuery(clazz);
        Root<T> entity = query.from(clazz);
        query.select(entity);
        if (orderField != null) {
            if (desc) {
                query.orderBy(builder.desc(entity.get(orderField)));
            } else {
                query.orderBy(builder.asc(entity.get(orderField)));
            }
        }
        TypedQuery<T> q = em.createQuery(query);
        q.setFirstResult(firstIndex);
        q.setMaxResults(pageSize);
        return q.getResultList();
    }

    @Override
    public <T extends ICommonEntity> List<T> findPaging(Class<T> clazz, int firstIndex, int pageSize, String orderField, boolean desc, Map<String, Object> filter) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<T> query = builder.createQuery(clazz);
        Root<T> entity = query.from(clazz);
        query.select(entity);
        if (orderField != null) {
            if (desc) {
                query.orderBy(builder.desc(entity.get(orderField)));
            } else {
                query.orderBy(builder.asc(entity.get(orderField)));
            }
        }
        if (filter != null && filter.size() > 0) {
            Iterator<String> iField = filter.keySet().iterator();
            String curField;
            Predicate[] pres = new Predicate[filter.size()];
            for (int i = 0; i < filter.size(); i++) {
                curField = iField.next();
                if (filter.get(curField) instanceof ICommonEntity) {
                    pres[i] = builder.equal(entity.<String>get(curField), filter.get(curField));
                } else {
                    pres[i] = builder.like(entity.<String>get(curField), "%" + filter.get(curField) + "%");
                }
            }
            query.where(pres);
        }
        TypedQuery<T> q = em.createQuery(query);
        q.setFirstResult(firstIndex);
        q.setMaxResults(pageSize);
        return q.getResultList();
    }

    @Override
    public <T extends ICommonEntity> long count(Class<T> clazz) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<Long> query = builder.createQuery(Long.class);
        query.select(builder.count(query.from(clazz)));
        TypedQuery<Long> q = em.createQuery(query);
        return q.getSingleResult();
    }

    @Override
    public <T extends ICommonEntity> long count(Class<T> clazz, Map<String, Object> filter) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<Long> query = builder.createQuery(Long.class);
        Root<T> entity = query.from(clazz);
        if (filter != null && filter.size() > 0) {
            Iterator<String> iField = filter.keySet().iterator();
            String curField;
            Predicate[] pres = new Predicate[filter.size()];
            for (int i = 0; i < filter.size(); i++) {
                curField = iField.next();
                if (filter.get(curField) instanceof ICommonEntity) {
                    pres[i] = builder.equal(entity.<String>get(curField), filter.get(curField));
                } else {
                    pres[i] = builder.like(entity.<String>get(curField), "%" + filter.get(curField) + "%");
                }
            }
            query.where(pres);
        }
        query.select(builder.count(entity));
        TypedQuery<Long> q = em.createQuery(query);
        return q.getSingleResult();
    }
}
